type Component
    protected
    Computed[vdom::Node];  // idea: Observable<VNode>

type ClassList
    List[String];

function Component:
    &(Computed[vdom::Node]) => Component
    &(c) => { |Component| c };

function Component:
    &(&() => vdom::Node) => Component
    native '__UiStaticComponent';

function Component:
    &(String) => Component
    &(tag) => { Component &() => { vdom::Node tag } };

function Component:
    &(String, String) => Component
    &(tag, text) => { Component &() => { vdom::Node (tag, text) } };

function Component:
    &(String, Computed[String]) => Component
    &(tag, text) => { Component (text map &(text) => { vdom::Node (tag, text) }) };

function Component:
    &(String, Computed[List[vdom::Node]]) => Component
    &(tag, children) =>
        { Component (children map &(children) => { vdom::Node (tag, children) }) };

function Component:
    &(String, List[Computed[vdom::Node]]) => Component
    &(tag, list) =>
        { Component (tag, {combine(list)}) };


export const Empty: Component
    ({ Component 'div' } with { InlineStyle [('display','none')] });

export function Box:
    &(ClassList) => &(List[Component]) => Component
    &(style) => &(children) =>
        ({ Component ('div',children) } with { Style style });

export function Label:
    &(String) => Component
    &(text) => { Component ('div', text) };

export function Text:
    &(Computed[String]) => Component
    &(text) => { Component ('div', text) };

export function Image:
    &(AssetImage) => Component
    &(img) =>
        let src := { get-asset-url img },
        { Component 'img' }
        . { with { Attr ('src',src) } }
        . { with { EventNoop 'dragstart.prevent' } };

export function Button:
    &(String, Sync) => Component
    &(text, action) =>
        { Component ('button', text) }
        . { with { Event ('click', { EventCallback action }) } };

export function TextInput:
    &(String,Sink[String]) => Component
    &(initial,@text) =>
        let input :=
            \ self::extract-string
            let value := 'webuiValue',
            { EventCallback (@text <- value) },
        \ do (@text <- initial),
        { Component 'input' }
        . { with { Attr ('type','text') } }
        . { with { Attr ('value',initial) } }
        . { with { Event ('input',input) } };

export function TextView:
    &(Reactive[String]) => Component
    &(buf) =>
        let edit := { EventCallbackAsync { |.[Async]|
            \ await
            let initial := buf.out.sample,
            \ await
            let line? := { modal-get-line {
                title: 'Edit Text', prompt: 'Edit Text:',
                initial
            } },
            switch line?:
            case Some line: (buf.in <- line),
            case None: Noop,
            end
        } },
        { Text (buf.out map &(text) => if (text = ''): '　', else: text) }
            . { with Focusable }
            . { with { Event ('keyup.key= ',edit) } }
            . { with { Event ('click',edit) } };

export function Checkbox:
    &(Reactive[Bool]) => Component
    &(@checked) =>
        let checked := @checked.out,
        let change :=
            \ self::extract-bool
            let checked := 'webuiChecked',
            { EventCallback (@checked.in <- checked) },
        { Component 'input' }
        . { with { Attr ('type','checkbox') } }
        . { with { Attr ('checked',checked) } }
        . { with { Event ('change',change) } };

export function Select:
    &(Reactive[String], List[(String,String)]) => Component
    &(@current, options) =>
        let current := @current.out,
        let change :=
            \ self::extract-string
            let value := 'webuiValue',
            { EventCallback (@current.in <- value) },
        { Component ('select', (options map &(name,desc) =>
            { Component ('option',desc) }
            . { with { Attr ('value',name) } }
        )) }
        . { with { Attr ('value',current) } }
        . { with { Event ('change',change) } };


type ListViewItemSupplier[T]
    &(ListEditKey,Computed[ListEditItemInfo],Reactive[T]) => Component;

export function ListView:[T]
    &(ListEdit[T], ListViewItemSupplier[T]) => Component
    &(edit, f) =>
        let children := (edit transform f),
        { Component ('div', children) };


export function SwitchComponent:
    &(Computed[Component]) => Component
    &(h) => { Component (h switch-map ||) };

export function MaybeComponent:[T]
    &(Reactive[Maybe[T]], &(Reactive[T]) => Component) => Component
    &(data, f) =>
        { Component
            \ switch-map
            let ok := (data.out map |.{Bool}|),
            if ok:
                \ do
                let relay := { Reactive::[T] { initial: UnsafeArbitraryValue } },
                let bubble := (data.in adapt::[Maybe[T],T] |.{Some}|),
                let client := { Reactive { in: bubble, out: relay.out } },
                ({ f client } with
                    \ -> let t? := data.out,
                    switch t?:
                    case Some t: (relay.in <- t),
                    case None: Noop,
                    end
                ),
            else:
                Empty
        };


